﻿/*
	This file is part of Limpet.

    Limpet is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Limpet is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with Limpet.  If not, see http://www.gnu.org/licenses/.
	
	Copyright 2010 Dan Popick
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text;
using System.Web;
using System.Web.Caching;
using System.Web.UI;
using System.Xml;

using log4net;

namespace Open.Web.Diagnostics.Limpet.Modules
{
    public class LimpetAppender : log4net.Appender.AppenderSkeleton
    {
        public LimpetAppender()
        {
        }

        protected override void Append(log4net.Core.LoggingEvent loggingEvent)
        {

            Log4NetModule.AddEvent(new Log4NetModule.LoggingEventEntry()
            {
                Timestamp = loggingEvent.TimeStamp,
                Severity = loggingEvent.Level,
                LoggerName = loggingEvent.LoggerName.Replace('.', '-'), // hack to encourage line wrapping
                Message = RenderLoggingEvent(loggingEvent)
            });
        }
    }

    /// <summary>
    /// A Limpet module which examines all currently loaded assemblies and reports
    /// whether they were compiled with optimizations
    /// </summary>
    public class Log4NetModule: ISelfRenderingLimpetModule
    {
        public class LoggingEventEntry
        {
            public DateTime Timestamp { get; set; }
            public log4net.Core.Level Severity { get; set; }
            public string LoggerName { get; set; }
            public string Message { get; set; }
        }

        static Queue<LoggingEventEntry> _entries = new Queue<LoggingEventEntry>();
        static int _maxEvents;
        static LimpetSeverity _severity;

        public int MaxEvents { get { return _maxEvents; } set { _maxEvents = value; } }
        public LimpetSeverity Severity { get { return _severity; } set { _severity = value; } }

        public Log4NetModule(string name, XmlNode configurationNode)
        {
            MaxEvents = 50;
            Name = name;
            _entries = new Queue<LoggingEventEntry>(MaxEvents);
        }

        static internal void AddEvent(LoggingEventEntry entry)
        {
            if (TranslateLevel(entry.Severity) < _severity)
                return;

            lock (_entries)
            {
                if (_entries.Count >= _maxEvents && _maxEvents > 0)
                    _entries.Dequeue();
                _entries.Enqueue(entry);
            }
        }

        #region ISelfRenderingLimpetModule Members

        /// <summary>
        /// Renders the HTML details for this module
        /// </summary>
        /// <param name="stream">A stream onto which the details are written</param>
        public void RenderDetails(Stream stream)
        {
            TextWriter tw = new StreamWriter(stream);
            HtmlTextWriter writer = new HtmlTextWriter(tw);

            writer.BeginRender();
            writer.RenderBeginTag(HtmlTextWriterTag.H2);
            writer.WriteEncodedText("Entries");
            writer.RenderEndTag();

            writer.RenderBeginTag(HtmlTextWriterTag.Table);
            writer.RenderBeginTag(HtmlTextWriterTag.Tr);
                writer.RenderBeginTag(HtmlTextWriterTag.Th);
                writer.WriteEncodedText("Time");
                writer.RenderEndTag();
                writer.RenderBeginTag(HtmlTextWriterTag.Th);
                writer.WriteEncodedText("Level");
                writer.RenderEndTag();
                writer.RenderBeginTag(HtmlTextWriterTag.Th);
                writer.WriteEncodedText("Logger");
                writer.RenderEndTag();
                writer.RenderBeginTag(HtmlTextWriterTag.Th);
                writer.WriteEncodedText("Message");
                writer.RenderEndTag();
            writer.RenderEndTag();

            List<LoggingEventEntry> list;
            lock (_entries)
            {
                list = new List<LoggingEventEntry>(_entries);
            }

            list.Reverse();
            foreach (LoggingEventEntry le in list)
            {
                writer.RenderBeginTag(HtmlTextWriterTag.Tr);
                writer.RenderBeginTag(HtmlTextWriterTag.Td);
                writer.WriteEncodedText(le.Timestamp.ToShortTimeString());
                writer.RenderEndTag();
                writer.RenderBeginTag(HtmlTextWriterTag.Td);
                writer.WriteEncodedText(le.Severity.ToString());
                writer.RenderEndTag();
                writer.RenderBeginTag(HtmlTextWriterTag.Td);
                writer.WriteEncodedText(le.LoggerName);
                writer.RenderEndTag();
                writer.RenderBeginTag(HtmlTextWriterTag.Td);


                string[] s = le.Message.Split('\n');
                int last = String.IsNullOrEmpty(s[s.Length - 1]) ? s.Length - 2 : s.Length - 1;
                for (int i = 0; i <= last; ++i)
                {
                    writer.WriteLine(s[i]);
                    if (i != last)
                        writer.WriteLine("<br />");
                }

                writer.RenderEndTag();
                writer.RenderEndTag();
            }

            writer.RenderEndTag();

            writer.Flush();
        }

        #endregion

        #region ILimpetModule Members

        /// <summary>
        /// The name, as defined in the config file
        /// </summary>
        public string Name { get; set; }

        static LimpetSeverity TranslateLevel(log4net.Core.Level level)
        {
            if (level >= log4net.Core.Level.Fatal)
                return LimpetSeverity.Fatal;
            if (level >= log4net.Core.Level.Error)
                return LimpetSeverity.Error;
            if (level >= log4net.Core.Level.Warn)
                return LimpetSeverity.Warning;
            if (level >= log4net.Core.Level.Info)
                return LimpetSeverity.Info;
            return LimpetSeverity.Debug;
        }

        /// <summary>
        /// Gets a single-line HTML message to display in the Limpet popup.
        /// </summary>
        /// <param name="context">The current HTTP context</param>
        /// <returns>A string of HTML to display in the popup.</returns>
        public SummaryMessage GetMessage(System.Web.HttpContext context)
        {
            string url = LimpetManager.GetSelfRenderingUrl(Name);
            LimpetSeverity sev = LimpetSeverity.Debug;
            string message = null;
            foreach (LoggingEventEntry le in _entries)
            {
                LimpetSeverity lesev = TranslateLevel(le.Severity);
                if (lesev >= sev || message == null)
                {
                    message = le.Message == null ? String.Empty : le.Message.Substring(0, Math.Min(le.Message.Length, 30));
                    sev = lesev;
                }
            }

            return new SummaryMessage()
            {
                Category = "Log4Net",
                Severity = sev,
                Text = message ?? String.Empty,
                Url = url
            };
        }

        #endregion
    }
}
